﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using LowLevelGraphics;
using System.Runtime.InteropServices;

using LowLevelGraphics.Filter;

namespace LowLevelGraphics.Filter
{
    /// <summary>
    /// 
    /// </summary>
    public class FishEye : BaseImageFilter
    {
        protected float m_fCurvature = 0.5f;

        protected GrayScale m_Grayscale = new GrayScale();

        #region IFilter Members

        /// <summary>
        /// Initializes a new instance of the <see cref="FishEye"/> class.
        /// </summary>
        public FishEye()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="FishEye"/> class.
        /// </summary>
        /// <param name="inCurvature">The in curvature.</param>
        public FishEye(float inCurvature)
        {
            m_fCurvature = inCurvature;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="FishEye"/> class.
        /// </summary>
        /// <param name="_fishEye">The _fish eye.</param>
        internal FishEye(FishEye _fishEye)
        {
            this.m_fCurvature = _fishEye.m_fCurvature;
        }

        /// <summary>
        /// Gets or sets the curvature.
        /// </summary>
        /// <value>The curvature.</value>
        public float Curvature
        {
            get { return m_fCurvature; }
            set { m_fCurvature = value; }
        }

        /// <summary>
        /// Execute this Filter
        /// A Debug Statement is written if a concrete Filter is called
        /// </summary>
        /// <param name="_bitmap"></param>
        /// <returns></returns>
        public override UnsafeBitmap Execute(UnsafeBitmap _bitmap)
        {
            Apply(_bitmap);
            return _bitmap;
        }

        /// <summary>
        /// Applies the specified _bitmap.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        /// <returns></returns>
        protected virtual UnsafeBitmap Apply(UnsafeBitmap _bitmap)
        {
            if (m_fCurvature <= 0.0f)
            {
                throw new ArgumentException("Curvature Must Be Set and It Must Be > 0.0f");
            }

            // get source image size
            int width = _bitmap.Width;
            int height = _bitmap.Height;
            int halfWidth = width / 2;
            int halfHeight = height / 2;

            //Obtain the Maxium Size Of the Fish Eye
            int maxRadius = (int)Math.Min(width, height) / 2;             //The S parameter mentioned in Jason Walton Algo.
            double s = maxRadius / Math.Log(m_fCurvature * maxRadius + 1);

            PixelFormat fmt = _bitmap.BitmapData.PixelFormat;            // lock source bitmap data
            BitmapData srcData = _bitmap.BitmapData;

            // create new image
            Bitmap dstImg = null;
            if (fmt == PixelFormat.Format8bppIndexed)
            {
                dstImg = (Bitmap)_bitmap.Clone();
                m_Grayscale.Execute(_bitmap);
            }
            else
            {
                dstImg = new Bitmap(width, height, fmt);
            }


            // lock destination bitmap data
            BitmapData dstData = dstImg.LockBits(
                new Rectangle(0, 0, width, height),
                ImageLockMode.ReadWrite, fmt);

            int stride = srcData.Stride;
            int offset = stride - ((fmt == PixelFormat.Format8bppIndexed) ? width : width * 3);

            Color RGB = Color.Transparent;

            // Perform The Fish Eye Conversion
            //
            unsafe
            {
                byte* src = (byte*)srcData.Scan0.ToPointer();
                byte* dst = (byte*)dstData.Scan0.ToPointer();
                byte* p;

                if (fmt == PixelFormat.Format8bppIndexed)
                {
                    #region Grayscale
                    for (int y = -1 * halfHeight; y < height - halfHeight; y++)
                    {
                        for (int x = -1 * halfWidth; x < width - halfWidth; x++)
                        {
                            byte r = Marshal.ReadByte(srcData.Scan0, *dst + 2);
                            byte g = Marshal.ReadByte(srcData.Scan0, *dst + 1);
                            byte b = Marshal.ReadByte(srcData.Scan0, *dst);

                            RGB = Color.FromArgb(r, g, b);
                            //Get the Current Pixels Polar Co-ordinates
                            double radius = Math.Sqrt(x * x + y * y);
                            double angle = Math.Atan2(y, x);

                            //Check to see if the polar pixel is out of bounds
                            if (radius <= maxRadius)
                            {
                                //Current Pixel is inside the Fish Eye
                                //newRad is the pixel that we want to shift to current pixel based on the fish eye posistion
                                double newRad = (Math.Exp(radius / s) - 1) / m_fCurvature;
                                int newX = (int)(newRad * Math.Cos(angle));
                                int newY = (int)(newRad * Math.Sin(angle));

                                newX += halfWidth;
                                newY += halfHeight;

                                p = src + newY * stride + newX;
                                byte* dst2 = dst + (y + halfHeight) * stride + (x + halfWidth);
                                dst2 = p;

                            }
                            else
                            {
                                *dst = src[y * stride + x];
                            }

                        }
                        dst += offset;
                    }
                    #endregion
                }
                else
                {
                    #region RGB

                    //Start from the middle because point (0,0) in cartesian co-ordinates should not be in the center
                    for (int y = -1 * halfHeight; y < height - halfHeight; y++)
                    {
                        for (int x = -1 * halfWidth; x < width - halfWidth; x++)
                        {
                            byte r = Marshal.ReadByte(srcData.Scan0, *dst + 2);
                            byte g = Marshal.ReadByte(srcData.Scan0, *dst + 1);
                            byte b = Marshal.ReadByte(srcData.Scan0, *dst);

                            RGB = Color.FromArgb(r, g, b);

                            //Get the Current Pixels Polar Co-ordinates
                            double radius = Math.Sqrt(x * x + y * y);
                            double angle = Math.Atan2(y, x);

                            //Check to see if the polar pixel is out of bounds
                            if (radius <= maxRadius)
                            {
                                //Current Pixel is inside the Fish Eye

                                //newRad is the pixel that we want to shift to current pixel based on the fish eye posistion
                                double newRad = (Math.Exp(radius / s) - 1) / m_fCurvature;
                                //Current Pixels Polar Cordinates Back To Cartesian
                                int newX = (int)(newRad * Math.Cos(angle));
                                int newY = (int)(newRad * Math.Sin(angle));

                                newX += halfWidth;
                                newY += halfHeight;

                                //Chage the Pointer to the Src
                                p = src + newY * stride + newX * 3;
                                //Change the Pointer to the destination
                                byte* dst2 = dst + (y + halfHeight) * stride + (x + halfWidth) * 3;
                                dst2[RGB.R] = p[RGB.R];
                                dst2[RGB.G] = p[RGB.G];
                                dst2[RGB.B] = p[RGB.B];

                            }
                            else
                            {
                                //Current Pixel is outside the Fish Eye thus should be output as is
                                p = src + (y + halfHeight) * stride + (x + halfWidth) * 3;

                                //Change the Pointer to the destination
                                byte* dst2 = dst + (y + halfHeight) * stride + (x + halfWidth) * 3;

                                dst2[RGB.R] = p[RGB.R];
                                dst2[RGB.G] = p[RGB.G];
                                dst2[RGB.B] = p[RGB.B];
                            }
                        }
                    }
                    #endregion
                }
            }
            // unlock both images
            dstImg.UnlockBits(dstData);

            BitmapFunctions.Copy(dstImg, _bitmap);
            return _bitmap;
        }
        #endregion

        /// <summary>
        /// This is used to clone a filter, each filter must implement it's own clone functionality
        /// </summary>
        /// <returns></returns>
        public override object Clone()
        {
            return new FishEye(this);
        }
    }
}

